///
// * jdbm LICENSE v1.00
// *
// * Redistribution and use of this software and associated documentation
// * ("Software"), with or without modification, are permitted provided
// * that the following conditions are met:
// *
// * 1. Redistributions of source code must retain copyright
// *    statements and notices.  Redistributions must also contain a
// *    copy of this document.
// *
// * 2. Redistributions in binary form must reproduce the
// *    above copyright notice, this list of conditions and the
// *    following disclaimer in the documentation and/or other
// *    materials provided with the distribution.
// *
// * 3. The name "jdbm" must not be used to endorse or promote
// *    products derived from this Software without prior written
// *    permission of Cees de Groot.  For written permission,
// *    please contact cg@cdegroot.com.
// *
// * 4. Products derived from this Software may not be called "jdbm"
// *    nor may "jdbm" appear in their names without prior written
// *    permission of Cees de Groot.
// *
// * 5. Due credit should be given to the jdbm Project
// *    (http://jdbm.sourceforge.net/).
// *
// * THIS SOFTWARE IS PROVIDED BY THE ndbm PROJECT AND CONTRIBUTORS
// * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
// * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
// * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// * OF THE POSSIBILITY OF SUCH DAMAGE.
// *
// * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
// * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
// * Contributions are Copyright (C) 2000 by their associated contributors.
// *
// * $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
// 

//*************************************************************************
//**  Included in JDMB 1.0 port to SharpDBM;  11/2013 Cyrus Neah cneah@codingreal.com
//**  SharpDBM is an independent reimplementation of the JDBM 1.0 software library in C#  
//*************************************************************************

using System.Runtime.CompilerServices;

namespace SharpDBM.recman
{

	using RecordManager = SharpDBM.RecordManager;
	using CacheEvictionException = SharpDBM.helper.CacheEvictionException;
	using CachePolicy = SharpDBM.helper.CachePolicy;
	using CachePolicyListener = SharpDBM.helper.CachePolicyListener;
	using DefaultSerializer = SharpDBM.helper.DefaultSerializer;
	using Serializer = SharpDBM.helper.Serializer;
	using WrappedRuntimeException = SharpDBM.helper.WrappedRuntimeException;
	using System;
	using System.Collections;
	using System.IO;
    using SharpDBM.helper;


///
// *  A RecordManager wrapping and caching another RecordManager.
// *
// * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
// * @author <a href="cg@cdegroot.com">Cees de Groot</a>
// * @version $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
// 
	public class CacheRecordManager : RecordManager
	{

//    *
//     * Wrapped RecordManager
//     
		protected internal RecordManager _recman;


//    *
//     * Cache for underlying RecordManager
//     
		protected internal CachePolicy _cache;


//    *
//     * Construct a CacheRecordManager wrapping another RecordManager and
//     * using a given cache policy.
//     *
//     * @param recman Wrapped RecordManager
//     * @param cache Cache policy
//     
		public CacheRecordManager(RecordManager recman, CachePolicy cache)
		{
			if (recman == null)
			{
				throw new ArgumentOutOfRangeException("Argument 'recman' is null");
			}
			if (cache == null)
			{
				throw new ArgumentOutOfRangeException("Argument 'cache' is null");
			}
			_recman = recman;
			_cache = cache;

			_cache.addListener(new CacheListener(this));
		}


//    *
//     * Get the underlying Record Manager.
//     *
//     * @return underlying RecordManager or null if CacheRecordManager has
//     *         been closed. 
//     
		public virtual RecordManager getRecordManager()
		{
			return _recman;
		}


//    *
//     * Get the underlying cache policy
//     *
//     * @return underlying CachePolicy or null if CacheRecordManager has
//     *         been closed. 
//     
		public virtual CachePolicy getCachePolicy()
		{
			return _cache;
		}


//    *
//     *  Inserts a new record using a custom serializer.
//     *
//     *  @param obj the object for the new record.
//     *  @return the rowid for the new record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		public virtual long insert(object obj)
		{
			return insert(obj, DefaultSerializer.INSTANCE);
		}


//    *
//     *  Inserts a new record using a custom serializer.
//     *
//     *  @param obj the object for the new record.
//     *  @param serializer a custom serializer
//     *  @return the rowid for the new record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual long insert(object obj, Serializer serializer)
		{
			checkIfClosed();

			long recid = _recman.insert(obj, serializer);
			try
			{
				_cache.put((recid), new CacheEntry(recid, obj, serializer, false));
			}
			catch (CacheEvictionException except)
			{
				throw new WrappedRuntimeException(except);
			}
			return recid;
		}


//    *
//     *  Deletes a record.
//     *
//     *  @param recid the rowid for the record that should be deleted.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void delete(long recid)
		{
			checkIfClosed();

			_recman.delete(recid);
			_cache.remove((recid));
		}


//    *
//     *  Updates a record using standard Java serialization.
//     *
//     *  @param recid the recid for the record that is to be updated.
//     *  @param obj the new object for the record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		public virtual void update(long recid, object obj)
		{
			update(recid, obj, DefaultSerializer.INSTANCE);
		}


//    *
//     *  Updates a record using a custom serializer.
//     *
//     *  @param recid the recid for the record that is to be updated.
//     *  @param obj the new object for the record.
//     *  @param serializer a custom serializer
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void update(long recid, object obj, Serializer serializer)
		{
			CacheEntry entry;
			long id;

			checkIfClosed();

			id = (recid);
			try
			{
				entry = (CacheEntry) _cache.get(id);
				if (entry != null)
				{
				// reuse existing cache entry
					entry._obj = obj;
					entry._serializer = serializer;
					entry._isDirty = true;
				}
				else
				{
					_cache.put(id, new CacheEntry(recid, obj, serializer, true));
				}
				}
			catch (CacheEvictionException except)
			{
				throw new IOException(except.Message);
			}
		}


//    *
//     *  Fetches a record using standard Java serialization.
//     *
//     *  @param recid the recid for the record that must be fetched.
//     *  @return the object contained in the record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		public virtual object fetch(long recid)
		{
			return fetch(recid, DefaultSerializer.INSTANCE);
		}


//    *
//     *  Fetches a record using a custom serializer.
//     *
//     *  @param recid the recid for the record that must be fetched.
//     *  @param serializer a custom serializer
//     *  @return the object contained in the record.
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual object fetch(long recid, Serializer serializer)
		{
			checkIfClosed();

			long id = (recid);
			CacheEntry entry = (CacheEntry) _cache.get(id);
			if (entry == null)
			{
				entry = new CacheEntry(recid, null, serializer, false);
				entry._obj = _recman.fetch(recid, serializer);
				try
				{
					_cache.put(id, entry);
				}
				catch (CacheEvictionException except)
				{
					throw new WrappedRuntimeException(except);
				}
			}
			return entry._obj;
		}


//    *
//     *  Closes the record manager.
//     *
//     *  @throws IOException when one of the underlying I/O operations fails.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void close()
		{
			checkIfClosed();

			updateCacheEntries();
			_recman.close();
			_recman = null;
			_cache = null;
		}


//    *
//     *  Returns the number of slots available for "root" rowids. These slots
//     *  can be used to store special rowids, like rowids that point to
//     *  other rowids. Root rowids are useful for bootstrapping access to
//     *  a set of data.
//     
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual int getRootCount()
		{
			checkIfClosed();

			return _recman.getRootCount();
		}


//    *
//     *  Returns the indicated root rowid.
//     *
//     *  @see #getRootCount
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual long getRoot(int id)
		{
			checkIfClosed();

			return _recman.getRoot(id);
		}


//    *
//     *  Sets the indicated root rowid.
//     *
//     *  @see #getRootCount
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void setRoot(int id, long rowid)
		{
			checkIfClosed();

			_recman.setRoot(id, rowid);
		}


//    *
//     * Commit (make persistent) all changes since beginning of transaction.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void commit()
		{
			checkIfClosed();
			updateCacheEntries();
			_recman.commit();
		}


//    *
//     * Rollback (cancel) all changes since beginning of transaction.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void rollback()
		{
			checkIfClosed();

			_recman.rollback();

		// discard all cache entries since we don't know which entries
		// where part of the transaction
			_cache.removeAll();
		}


//    *
//     * Obtain the record id of a named object. Returns 0 if named object
//     * doesn't exist.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual long getNamedObject(string name)
		{
			checkIfClosed();

			return _recman.getNamedObject(name);
		}


//    *
//     * Set the record id of a named object.
//     
 
//
		[MethodImpl(MethodImplOptions.Synchronized)]
		public virtual void setNamedObject(string name, long recid)
		{
			checkIfClosed();

			_recman.setNamedObject(name, recid);
		}


//    *
//     * Check if RecordManager has been closed.  If so, throw an
//     * InvalidOperationException
//     
 
//
		private void checkIfClosed()
		{
			if (_recman == null)
			{
				throw new InvalidOperationException("RecordManager has been closed");
			}
		}


//    *
//     * Update all dirty cache objects to the underlying RecordManager.
//     

		protected internal virtual void updateCacheEntries()
		{
            IEnumerator enume = _cache.elements();

            enume.Reset();
            while (enume.MoveNext())
			{

                CacheEntry entry = (CacheEntry)enume.Current;
				
                if (entry._isDirty)
				{
					_recman.update(entry._recid, entry._obj, entry._serializer);
					entry._isDirty = false;
				}

			}
		}


		private class CacheEntry
		{

			internal long _recid;
			internal object _obj;
			internal Serializer _serializer;
			internal bool _isDirty;

			internal CacheEntry(long recid, object obj, Serializer serializer, bool isDirty)
			{
				_recid = recid;
				_obj = obj;
				_serializer = serializer;
				_isDirty = isDirty;
			}

		} // class CacheEntry

		private class CacheListener : CachePolicyListener
		{

//        * Notification that cache is evicting an object
//         *
//         * @arg obj object evited from cache
//         *
			private CacheRecordManager loc_recman;
			public CacheListener(CacheRecordManager recman)
			{
				this.loc_recman = recman;
			}

			public virtual void cacheObjectEvicted(object obj)
			{
				CacheEntry entry = (CacheEntry) obj;
				if (entry._isDirty)
				{
					try
					{
						loc_recman.update(entry._recid, entry._obj, entry._serializer);
					}
					catch (IOException except)
					{
						throw new CacheEvictionException(except);
					}
				}
			}

		}
	}

}